package org.urchr.beorganized.dao;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.TreeSet;

import javax.servlet.ServletContext;

import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.Recur;
import net.fortuna.ical4j.model.TimeZoneRegistry;
import net.fortuna.ical4j.model.TimeZoneRegistryFactory;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.component.VTimeZone;
import net.fortuna.ical4j.model.property.CalScale;
import net.fortuna.ical4j.model.property.Name;
import net.fortuna.ical4j.model.property.ProdId;
import net.fortuna.ical4j.model.property.RRule;
import net.fortuna.ical4j.model.property.Summary;
import net.fortuna.ical4j.model.property.Uid;
import net.fortuna.ical4j.model.property.Version;
import net.fortuna.ical4j.model.property.XProperty;

import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.urchr.beorganized.api.base.Log;
import org.urchr.beorganized.dao.PermissionsDAO.PermissionType;
import org.urchr.beorganized.dao.errors.DBErrorException;
import org.urchr.beorganized.dao.errors.GeneralException;
import org.urchr.beorganized.dao.errors.InvalidEndDateException;
import org.urchr.beorganized.dao.errors.InvalidFileException;
import org.urchr.beorganized.dao.errors.InvalidNameException;
import org.urchr.beorganized.dao.errors.InvalidStartDateException;
import org.urchr.beorganized.dao.errors.InvalidURLException;
import org.urchr.beorganized.dao.errors.NameTooShortException;
import org.urchr.beorganized.dao.errors.NoPermissionException;
import org.urchr.beorganized.dao.errors.StartDateLessEndDateException;
import org.urchr.beorganized.dao.errors.URLTooLongException;
import org.urchr.beorganized.dao.tables.Note;
import org.urchr.beorganized.dao.tables.Permission;
import org.urchr.beorganized.dao.tables.Star;
import org.urchr.beorganized.dao.tables.Tag;
import org.urchr.beorganized.dao.tables.User;
import org.urchr.web.db.DBSession;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class NotesDAO {
	
	public static final String FILES_STORE = "/WEB-INF/files/";
	public static final int FILES_MAX_SIZE = 5*1024*1024;
	
	
	public static int clearCompletedTasks(int noteID, int userID) throws GeneralException{
		DBSession session = new DBSession();
		try {
			User user = UsersDAO.getCurrent(session, userID);
			Note note = (Note) session.get(Note.class, noteID);
			if(!PermissionsDAO.hasPermission(session, userID, note, PermissionType.DELETE))
				throw new NoPermissionException();			
			List<Note> result = session.createQuery("from Note n where n.user=:user" +
					" and n.left>:left and n.right<:right and n.archived=0" +
					" and n.type=:type_task and n.completed=1").
				setEntity("user", user).
				setInteger("left", note.getLeft()).
				setInteger("right", note.getRight()).
				setInteger("type_task", Note.TYPE_TASK).
			list();
			int resultCount = 0;
			for (Note n : result) {
				archiveNote(userID, n.getId());
				resultCount++;
			}
			return resultCount;
		} catch (HibernateException e) {
			e.printStackTrace();
			throw new DBErrorException();
		}finally{
			session.close();
		}
	}
	
	public static int clearPastEvents(int noteID, int userID) throws GeneralException{
		DBSession session = new DBSession();
		try {
			User user = UsersDAO.getCurrent(session, userID);
			Note note = (Note) session.get(Note.class, noteID);
			if(!PermissionsDAO.hasPermission(session, userID, note, PermissionType.DELETE))
				throw new NoPermissionException();			
			List<Note> result = session.createQuery("from Note n where n.user=:user" +
					" and n.left>:left and n.right<:right and n.archived=0" +
					" and n.type=:type_event").
				setEntity("user", user).
				setInteger("left", note.getLeft()).
				setInteger("right", note.getRight()).
				setInteger("type_event", Note.TYPE_EVENT).
			list();
			int resultCount = 0;
			final Calendar nowGMT = Note.getGMTNow();
			for (Note n : result) {
				if(n.getEndDate().compareTo(nowGMT.getTime())<0){
					archiveNote(userID, n.getId());
					resultCount++;					
				}
			}
			return resultCount;
		} catch (HibernateException e) {
			e.printStackTrace();
			throw new DBErrorException();
		}finally{
			session.close();
		}
	}
	
	public static void completeTask(int noteID, int userID) throws GeneralException{
		DBSession session = new DBSession();
		try {
			Note note = (Note) session.get(Note.class, noteID);
			if(!PermissionsDAO.hasPermission(session, userID, note, PermissionType.EDIT) 
					|| note.getType()!=Note.TYPE_TASK)
				throw new NoPermissionException();
			session.begin();
			note.setCompleted(1);
			User user = UsersDAO.getCurrent(session, userID);
			if(note.getStartDate()!=null){
				note.initRecurrence(true);
				if(note.isRecurred()){
					repeatNote(user, note, session, false);
					note.setCompleted(0);
				}
			}
			note.setEditDate(new Date());
			note.setEditUser(user);
			session.commit();
		} catch (HibernateException e) {
			session.rollback();
			e.printStackTrace();
			throw new DBErrorException();
		}finally{
			session.close();
		}
	}
	
	public static Element getTaskList(Element root, XMLDocumentBuilder doc, 
			int aNoteID, int aUserID, boolean aAppendPaths, boolean aAddTags, 
			String aTagsFilter, int aNextDays, boolean aRenderCalendar, Date aCalendarDate) throws GeneralException{
		DBSession session = new DBSession();
		if(aRenderCalendar)
			aNextDays = 0;
		try {
			Calendar lCalendarDateCalendar = Calendar.getInstance();
			if(aCalendarDate!=null){
				lCalendarDateCalendar.setTime(aCalendarDate);
			}
			final User user = UsersDAO.getCurrent(session, aUserID);
			
			final Calendar nowUser = aRenderCalendar?lCalendarDateCalendar:Note.getUserNow(user);
			
			Calendar endUser = aRenderCalendar?lCalendarDateCalendar:Note.getUserNow(user);
			
			endUser.add(Calendar.DAY_OF_YEAR, aNextDays);
			
			List<String> filter = new ArrayList<String>();
			StringTokenizer tokenizer = new StringTokenizer(aTagsFilter, ",");
			while(tokenizer.hasMoreElements()){
				filter.add(tokenizer.nextToken().toLowerCase().trim());
			}
			Note note = (Note) session.get(Note.class, aNoteID);
			if(!PermissionsDAO.hasPermission(session, aUserID, note, PermissionType.VIEW))
				throw new NoPermissionException();
			List<Note> result = session.createQuery("from Note n where n.user=:user" +
					" and n.left>=:left and n.right<=:right and n.archived=0" +
					" and ((n.type=:type_task and n.completed=0) or " +
					"n.type=:type_app)").
				setEntity("user", note.getUser()).
				setInteger("left", note.getLeft()).
				setInteger("right", note.getRight()).
				setInteger("type_task", Note.TYPE_TASK).
				setInteger("type_app", Note.TYPE_EVENT).
			list();
			for (int i = 0; i < result.size(); i++) {
				Note n = result.get(i);
				n.initNote(user);
				if(n.getType()==Note.TYPE_EVENT && n.isRecurred()){
					session.begin();
					if(repeatNote(user, n, session, false)){
						n.initNote(user);
					}
					session.commit();
				}
				session.getSession().setReadOnly(n, true);
				if(aRenderCalendar){
					if(n.getStartDate()==null){
						result.remove(i);
						i--;
						continue;						
					}
					if(n.isRecurred()){
						//Scroll to current date
						while(Note.compareDates(n.getStartDate(),
								n.getType()==Note.TYPE_EVENT?user:null, nowUser.getTime(), null)<0){
							repeatNote(user, n, session, true);
						}
					}
					if(n.getType()==Note.TYPE_TASK && 
							Note.compareDates(n.getStartDate(), null, nowUser, null)<0){
						result.remove(i);
						i--;
						continue;
					}
				}else{
				}
				if(n.getType()==Note.TYPE_EVENT  && 
						Note.compareDates(n.getEndDate(), user, nowUser, null)<0){
					result.remove(i);
					i--;
					continue;
				}					
				if(n.getType()==Note.TYPE_TASK && n.getStartDate()!=null && 
						Note.compareDates(n.getStartDate(), null, endUser, null)>0){
					result.remove(i);
					i--;
					continue;
				}
				if(n.getType()==Note.TYPE_EVENT  && 
						Note.compareDates(n.getStartDate(), user, endUser, null)>0){
					result.remove(i);
					i--;
					continue;
				}
			}
			Collections.sort(result, new Comparator<Note>(){
				public int compare(Note o1, Note o2) {
//					System.out.println("o1:"+o1.getName()+"["+(o1.getType()==Note.TYPE_TASK?"TASK":"EVENT")+"] - o2:"+
//							o2.getName()+"["+(o2.getType()==Note.TYPE_TASK?"TASK":"EVENT")+"]");
					if(o1.getType()==Note.TYPE_TASK && o2.getType()==Note.TYPE_TASK){
						//Compare tasks
						if(o1.getStartDate()==null && o2.getStartDate()!=null){
							int result = Note.compareDates(o2.getStartDate(), null, nowUser, null);
//							System.out.println("null - !null["+o2.getStartDate()+"]: "+result);
							return result>0?-1:1;
						}
						if(o1.getStartDate()!=null && o2.getStartDate()==null){
							int result = Note.compareDates(o1.getStartDate(), null, nowUser, null);
//							System.out.println("!null["+o1.getStartDate()+"] - null: "+result);
							return result>0?1:-1;
						}
						if(o2.getStartDate()!=null && o1.getStartDate()!=null){
							int result = Note.compareDates(o1.getStartDate(), o2.getStartDate());
							if(result==0){
								if(o1.getPriority()!=o2.getPriority())
									result = o1.getPriority()>o2.getPriority()?1:-1;
								else
									result = o1.getName().compareTo(o2.getName());
							}
//							System.out.println("!null["+o1.getStartDate()+"] - !null["+o2.getStartDate()+"]: "+result);
							return result;//Check priority
						}
						if(o2.getStartDate()==null && o1.getStartDate()==null){
							int result = 0;
							if(result==0){
								if(o1.getPriority()!=o2.getPriority())
									result = o1.getPriority()>o2.getPriority()?1:-1;
								else
									result = o1.getName().compareTo(o2.getName());
							}
//							System.out.println("Both nulls: "+result);
							return result;//Check priority
						}
					}
					if(o1.getType()==Note.TYPE_EVENT && o2.getType()==Note.TYPE_EVENT){
						int result = Note.compareDates(o1.getStartDate(), o2.getStartDate());
						if(result==0){
							result = o1.getStartDate().compareTo(o2.getStartDate());
							if(result==0)
								result = o1.getName().compareTo(o2.getName());
						}
//						System.out.println("!null["+o1.getStartDate()+"] - !null["+o2.getStartDate()+"]: "+result);
						return result;//Check priority
					}
					if(o1.getType()==Note.TYPE_EVENT && o2.getType()==Note.TYPE_TASK){
						Calendar cal = Calendar.getInstance();
						cal.setTime(o1.getStartDate());
						cal.add(Calendar.MINUTE, user.getMinuteOffsetByDate(o1.getStartDate()));//Start date in user TZ
						if(o2.getStartDate()==null){
							int result = Note.compareDates(cal, null, nowUser, null);
//							System.out.println("!null["+o1.getStartDate()+"] - null: "+result);
							return result>0?1:-1;
						}
						int result = Note.compareDates(cal, null, o2.getStartDate(), null);
//						System.out.println("!null["+cal.getTime()+"] - !null["+o2.getStartDate()+"]: "+
//								result+" - "+(result>0?1:-1));
						return result>0?1:-1;
					}
					if(o1.getType()==Note.TYPE_TASK && o2.getType()==Note.TYPE_EVENT){
						Calendar cal = Calendar.getInstance();
						cal.setTime(o2.getStartDate());
						cal.add(Calendar.MINUTE, user.getMinuteOffsetByDate(o2.getStartDate()));//Start date in user TZ
						if(o1.getStartDate()==null){
							int result = Note.compareDates(cal, null, nowUser, null);
//							System.out.println("null - !null["+o2.getStartDate()+"]: "+result);
							return result>0?-1:1;
						}
						int result = Note.compareDates(o1.getStartDate(), null, cal, null);
//						System.out.println("!null["+o1.getStartDate()+"] - !null["+cal.getTime()+"]: "+
//								result+" - "+(result<0?-1:1));
						return result<0?-1:1;
					}
//					System.out.println("Something wrong!");
					return 0;
				}
			});
			
			Set<String> tags = new TreeSet<String>();
			
			for (Note n : result) {
				Element el = null;
				el = doc.createElement(n.getType()==Note.TYPE_EVENT?"event":"task");
				noteToElement(n, el);
				el.setAttribute("urgent", ""+n.getUrgentLevel());
				el.setAttribute("dates", n.getDatesInfo());
				el.setAttribute("recurred", ""+n.isRecurred());
				el.setAttribute("recurrenceInfo", n.getRecurrenceInfo());
				if(n.getType()==Note.TYPE_TASK){
					el.setAttribute("add_icon", "priority/p"+n.getPriority()+".png");
				}else{
					el.setAttribute("startDate", Note.dateToUserISO(n.getStartDate(), user, true, false));					
					el.setAttribute("endDate", Note.dateToUserISO(n.getEndDate(), user, true, false));					
				}
				
				if(aRenderCalendar && n.getType()==Note.TYPE_EVENT){
					//Fill additional fields for calendar
					Calendar c1 = Calendar.getInstance();
					c1.setTime(n.getStartDate());
					c1.add(Calendar.MINUTE, user.getMinuteOffsetByDate(n.getStartDate()));
					int lStartMinutes = c1.get(Calendar.HOUR_OF_DAY)*60+c1.get(Calendar.MINUTE);
					int lDuration = n.getDurationInMinutes();
					int liCompareStartDate = Note.compareDates(n.getStartDate(), user, nowUser, null);
					int liCompareEndDate = Note.compareDates(n.getEndDate(), user, endUser, null);
					
					if(liCompareStartDate==0){
						if(liCompareEndDate>0){
							lDuration = 1440-lStartMinutes;
						}
					}else{
						if(liCompareEndDate>0)
							lDuration = 1440;
						else{
							Calendar c2 = Calendar.getInstance();
							c2.setTime(n.getEndDate());
							c2.add(Calendar.MINUTE, user.getMinuteOffsetByDate(n.getEndDate()));
							lDuration = c2.get(Calendar.HOUR_OF_DAY)*60+c2.get(Calendar.MINUTE);
						}
						lStartMinutes = 0;
					}
					
					el.setAttribute("start", ""+lStartMinutes);
					el.setAttribute("duration", ""+lDuration);
					el.setAttribute("eventDates", n.getShortDatesInfo());
					
				}
				
				if(aAddTags || filter.size()>0){
					List<Tag> tagList = session.createQuery("from Tag where note=:note order by name").
							setEntity("note", n).
						list();
						List<String> noteTags = new ArrayList<String>();
					for (Tag tag : tagList) {
						noteTags.add(tag.getName());
					}
					if(filter.size()>0 && !noteTags.containsAll(filter)){
						continue;
					}
					if(aAddTags){
						tags.addAll(noteTags);
					}
				}
				if(aAppendPaths){
					List<Note> breadCrumb = session.createQuery("from Note n where " +
							"n.left>:left_top and n.left<:left_bottom and "+
							"n.right<:right_top and n.right>:right_bottom and " +
							"n.user=:user order by n.left").
						setInteger("left_top", note.getLeft()).
						setInteger("left_bottom", n.getLeft()).
						setInteger("right_top", note.getRight()).
						setInteger("right_bottom", n.getRight()).
						setEntity("user", note.getUser()).
					list();
					for (Note p : breadCrumb) {
						Element parent = doc.createElement("parent");
						noteToElement(p, parent);
						el.appendChild(parent);
					}	
				}
				root.appendChild(el);
			}
			if(aAddTags){
				for (String tag : tags) {
					Element t = doc.createElement("tag");
					t.setAttribute("name", tag);
					root.appendChild(t);
				}
			}
		} catch (HibernateException e) {
			e.printStackTrace();
			throw new DBErrorException();
		}finally{
			session.close();
		}
		return root;
	}
	
	public static Note getCurrent(int current, int userID) throws DBErrorException{
		DBSession session = new DBSession();
		try {
			if(current!=-1){
				Note note = (Note) session.get(Note.class, current);
				if(note!=null && PermissionsDAO.hasPermission(session, userID, note, PermissionType.VIEW)){
					return note;
				}
			}
			List<Permission> perms = PermissionsDAO.getPermissions(session, userID);
			return perms.get(0).getNote();
		} catch (HibernateException e) {
			e.printStackTrace();
		} finally {
			session.close();
		}
		throw new DBErrorException();
	}
	
	public static Note getNote(int noteID, int userID) throws DBErrorException{
		DBSession session = new DBSession();
		try {
			Note note = (Note) session.get(Note.class, noteID);
			if(PermissionsDAO.hasPermission(session, userID, note, PermissionType.VIEW)){
				return note;
			}
		} catch (HibernateException e) {
			e.printStackTrace();
		} finally {
			session.close();
		}
		throw new DBErrorException();
	}
	
	public static Note getNoteByCode(String shareCode, boolean fetchTags){
		DBSession session = new DBSession();
		try {
			List<Note> notes = session.createQuery("from Note where shareCode=:code")
					.setString("code", shareCode)
				.list();
			if(notes.size()>0){
				Note note = notes.get(0);
				if(fetchTags){
					List<Tag> tagList = session.createQuery("from Tag where note=:note order by name").
					setEntity("note", note).
						list();
					String tags = "";
					for (Tag tag : tagList) {
						if(!"".equals(tags))
							tags += ", ";
						tags += tag.getName();
					}
					note.setTagsString(tags);
				}
				return note;
			}
		} catch (HibernateException e) {
			e.printStackTrace();
		} finally {
			session.close();
		}
		return null;
	}
	
	private static int loadTree(DBSession session, XMLDocumentBuilder doc, 
			List<Note> parents, Node parent, int i, User user){
		Note p = parents.get(i);
		List<Note> childs = session.createQuery("select n from Note n " +
				"where n.left>:left and n.right<:right and level=:level and user=:user and n.archived=0 order by "+p.getOrderBy()).
				setInteger("left", p.getLeft()).
				setInteger("right", p.getRight()).
				setInteger("level", p.getLevel()+1).
				setEntity("user", user).
				list();
		int numberOfItems = 0;
		for (Note note : childs) {
			Element el = doc.createElement("node");
			noteToElement(note, el);
			parent.appendChild(el);
			int numberOfChildren = 0;
			if(parents.size()-1>i && parents.get(i+1).getId()==note.getId())
				numberOfChildren = loadTree(session, doc, parents, el, i+1, user);
			else{
//				Number number = (Number) session.createQuery("select count(*) from Note n where " +
//						"n.left>:left and n.right<:right and n.user=:user and n.archived=0").
//						setInteger("left", note.getLeft()).
//						setInteger("right", note.getRight()).
//						setEntity("user", note.getUser()).uniqueResult();
//				numberOfChildren = number.intValue();
			}
			numberOfItems += numberOfChildren+1;
//			if(numberOfChildren>0)
//				el.setAttribute("info", "("+numberOfChildren+")");
		}
		return numberOfItems;
	}
	
	public static void getSubTree(Note note, DBSession session, XMLDocumentBuilder doc, 
			Node node, User user, boolean loadData, boolean loadChildren) throws DBErrorException, NoPermissionException{
		if(note==null){
//			System.out.println("Requesting roots... user = "+user);
			List<Permission> roots = PermissionsDAO.getPermissions(session, user.getId());
//			System.out.println("roots.size = "+roots.size());
			for (Permission perm : roots) {
				Element parent = doc.createElement("note");
				if(loadData)
					fullNoteToElement(doc, parent, perm.getNote().getId(), user.getId(), false);
				else
					noteToElement(perm.getNote(), parent);
				node.appendChild(parent);
				if(loadChildren)
					getSubTree(perm.getNote(), session, doc, parent, perm.getNote().getUser(), loadData, loadChildren);
			}
		}else{
//			System.out.println("Requesting childs");
			List<Note> childs = session.createQuery("select n from Note n " +
					"where n.left>:left and n.right<:right and level=:level and user=:user and n.archived=0 order by "+
					note.getOrderBy()).
						setInteger("left", note.getLeft()).
						setInteger("right", note.getRight()).
						setInteger("level", note.getLevel()+1).
						setEntity("user", user).
					list();
//			System.out.println("Childs count = "+childs.size());
			for (Note n : childs) {
				Element parent = doc.createElement("note");
				if(loadData)
					fullNoteToElement(doc, parent, n.getId(), user.getId(), false);
				else
					noteToElement(n, parent);
				node.appendChild(parent);
				if(loadChildren)
					getSubTree(n, session, doc, parent, n.getUser(), loadData, loadChildren);
			}
		}
	}
	
	public static Element getTree(int noteID, int userID, boolean loadData, boolean loadChildren) throws DBErrorException{
//		System.out.println("getTree: noteID = "+noteID);
		XMLDocumentBuilder doc = new XMLDocumentBuilder();
		doc.getRootElement("note");
		DBSession session = new DBSession();
		try {
			if(noteID==-1){
//				System.out.println("Loading roots first, userID = "+userID);
				doc.getRootElement().setAttribute("id", "-1");
				doc.getRootElement().setAttribute("parentid", "-1");
				doc.getRootElement().setAttribute("text", "Root");
				getSubTree(null, session, doc, doc.getRootElement(), 
						UsersDAO.getCurrent(session, userID), loadChildren?loadData:false, loadChildren);
			}else{
//				System.out.println("Direct note");
				Note note = getNote(noteID, userID);
				if(note.getLevel()>0){
					Note parentNote = (Note) session.createQuery("from Note n where " +
							"n.left<:left and n.right>:right and n.archived=0 " +
							"and n.user=:user and n.level=:level").
							setInteger("left", note.getLeft()).
							setInteger("right", note.getRight()).
							setEntity("user", UsersDAO.getUser(userID)).
							setInteger("level", note.getLevel()-1).
						uniqueResult();
					if(parentNote!=null)
						doc.getRootElement().setAttribute("parentid", ""+parentNote.getId());
					else
						doc.getRootElement().setAttribute("parentid", "-1");					
				}else{
					doc.getRootElement().setAttribute("parentid", "-1");					
				}
				if(loadData)
					fullNoteToElement(doc, doc.getRootElement(), noteID, userID, false);
				else
					noteToElement(note, doc.getRootElement());
				getSubTree(note, session, doc, doc.getRootElement(), 
						note.getUser(), loadChildren?loadData:false, loadChildren);
			}			
		} catch (Exception e) {
			e.printStackTrace();
			throw new DBErrorException();
		} finally {
			session.close();
		}
		return doc.getRootElement();
	}
	
	//SHORT NOTE XML: ID, NAME, TYPE, ICON, INFO
	public static Node getTreeFragment(Note current, int userID) throws DBErrorException{
		XMLDocumentBuilder doc = new XMLDocumentBuilder();
		DBSession session = new DBSession();
		try {
			List<Permission> roots = PermissionsDAO.getPermissions(session, userID);
			int i = 0;
			for (Permission perm : roots) {
				Element parent = doc.createElement("note");
				List<Note> parents = session.createQuery("select n from Note n where " +
					"n.left<=:left and n.right>=:right and n.user=:user and n.level>=:level" +
					" and n.archived=0 order by n.level")
						.setInteger("left", current.getLeft())
						.setInteger("right", current.getRight())
						.setEntity("user", perm.getNote().getUser())
						.setInteger("level", perm.getNote().getLevel())
						.setCacheable(true)
					.list();
				noteToElement(perm.getNote(), parent);
				parent.setAttribute("info", i==0?"my notes":"from "+perm.getNote().getUser().getLogin());
				doc.getRootElement().appendChild(parent);
				// && userID==perm.getNote().getUser().getId()
				if(parents.size()>0)
					loadTree(session, doc, parents, parent, 0, perm.getNote().getUser());				
				i++;
			}
		} catch (Exception e) {
			throw new DBErrorException();
		} finally {
			session.close();
		}
//		System.out.println("tree = "+doc.makeXML());
		return doc.getRootElement();
	}
	
	private static void noteToElement(Note note, Element e){
		e.setAttribute("id", ""+note.getId());
		e.setAttribute("text", note.getName());
		e.setAttribute("type", ""+note.getType());
		e.setAttribute("icon", ""+note.getCustomIcon());
		if(note.getType()==Note.TYPE_TASK && note.getCompleted()>0)
			e.setAttribute("add_icon", "action/complete.png");
	}
	
	public static Element fullNoteToElement(XMLDocumentBuilder doc, Element e, int noteID, int userID, boolean loadTags) throws DBErrorException, NoPermissionException{
		DBSession session = new DBSession();
		try {
//			XMLDocumentBuilder doc = new XMLDocumentBuilder();
//			doc.getRootElement("note");
			Note note = (Note) session.get(Note.class, noteID);
			User user = UsersDAO.getCurrent(session, userID);
			note.initNote(user);
			if(note.getType()==Note.TYPE_EVENT && note.isRecurred()){
				session.begin();
				if(repeatNote(user, note, session, false))
					note.initNote(user);
				session.commit();
			}
			Permission perm = PermissionsDAO.getPermission(session, userID, note);
			if(perm==null)
				throw new NoPermissionException();
//			Element e = doc.getRootElement();
			e.setAttribute("id", ""+note.getId());
			e.setAttribute("text", note.getName());
			e.setAttribute("type", ""+note.getType());
			e.setAttribute("icon", ""+note.getType());		
			
			e.setAttribute("customIcon", ""+note.getCustomIcon());
			e.setAttribute("contentType", note.getContentType());
			e.setAttribute("datesInfo", note.getDatesInfo());
			e.setAttribute("recurrenceInfo", note.getRecurrenceInfo());
			e.setAttribute("recurred", ""+note.isRecurred());
			e.setAttribute("fileName", note.getFileName());
			e.setAttribute("shareCode", note.getShareCode());
			e.setAttribute("recurrence", note.getRecurrence());
			e.setAttribute("textContent", note.getTextContent());
			e.setAttribute("completed", ""+(note.getCompleted()>0));
			e.setAttribute("editUser", note.getEditUser().getLogin());
			e.setAttribute("user", note.getUser().getLogin());
			e.setAttribute("fileSize", ""+note.getFileSize());
			e.setAttribute("level", ""+note.getLevel());
			e.setAttribute("priority", ""+note.getPriority());
			e.setAttribute("sort", ""+note.getSort());
			e.setAttribute("sortType", ""+note.getSortType());
			e.setAttribute("urgentLevel", ""+note.getUrgentLevel());
			e.setAttribute("url", note.getUrl());
			
			e.setAttribute("createDate", Note.dateToUserISO(note.getCreateDate(), user, false, false));
			e.setAttribute("editDate", Note.dateToUserISO(note.getEditDate(), user, false, false));
			e.setAttribute("startDate", 
					Note.dateToUserISO(note.getStartDate(), user, true, note.getType()==Note.TYPE_TASK));
			e.setAttribute("endDate", Note.dateToUserISO(note.getEndDate(), user, true, false));
			
			e.setAttribute("canCreate", ""+(perm.getAllowCreate()>0));
			e.setAttribute("canEdit", ""+(perm.getAllowEdit()>0));
			e.setAttribute("canDelete", ""+(perm.getAllowDelete()>0));
			if(!loadTags)
				return e;
			Element tagsElement = doc.createElement("tags_my");
			e.appendChild(tagsElement);
			List<Tag> tagList = session.createQuery("from Tag where note=:note order by name").
					setEntity("note", note).
				list();
			String tags = "";
			for (Tag tag : tagList) {
				if(!"".equals(tags))
					tags += ", ";
				tags += tag.getName();
				Element t = doc.createElement("tag");
				t.setAttribute("id", ""+tag.getId());
				t.setAttribute("name", tag.getName());
				tagsElement.appendChild(t);
			}
			
			e.setAttribute("tags", tags);
			Note root = PermissionsDAO.getPermission(userID, note).getNote();
			
			Element tagsAllElement = doc.createElement("tags_all");
			e.appendChild(tagsAllElement);
			
			List<Object[]> allTags = session.createQuery("select t.name, count(*) from Tag t, Note n " +
					"where t.note=n and n.left>=:left and n.right<=:right and n.user=:user and n.archived=0 " +
					"group by t.name order by t.name").
						setInteger("left", root.getLeft()).
						setInteger("right", root.getRight()).
						setEntity("user", root.getUser()).
					list();					
			
			for (Object[] objects : allTags) {
				Element t = doc.createElement("tag");
				t.setAttribute("name", objects[0].toString());				
				t.setAttribute("count", objects[1].toString());
				tagsAllElement.appendChild(t);
			}
			
			List<Star> star = session.createQuery("from Star where note=:note and user=:user").
					setEntity("note", note).
					setEntity("user", user).
				list();
			e.setAttribute("starred", new Boolean(!star.isEmpty()).toString());
			return e;			
		} catch (HibernateException ex) {
			// TODO: handle exception
			throw new DBErrorException();
		} finally {
			session.close();
		}
	}
	
	public static int safeInt(String s, int d){
		if(s==null || s.equals(""))
			return d;
		try {
			return Integer.parseInt(s);
		} catch (Exception e) {
		}
		return d;
	}
	
//	public static Note nodeToNote1(Element e, int userID) throws DBErrorException{
//		DBSession session = new DBSession();
//		try {
//			User user = UsersDAO.getCurrent(new DBSession(), userID);
//			Note note = new Note();
//			
//			note.setType(safeInt(e.getAttribute("type"), 0));
//			note.setCustomIcon(safeInt(e.getAttribute("customIcon"), note.getType()));
//			
//			note.setId(safeInt(e.getAttribute("id"), -1));
//			note.setName(e.getAttribute("text"));
//			note.setContentType(e.getAttribute("contentType"));
//			note.setFileName(e.getAttribute("fileName"));
//			note.setCompleted("true".equals(e.getAttribute("completed"))?1:0);
//			
//			note.setFileSize(safeInt(e.getAttribute("fileSize"), 0));
//			note.setUrl(e.getAttribute("url"));
//			note.setPriority(safeInt(e.getAttribute("priority"), 2));
//			note.setSort(safeInt(e.getAttribute("sort"), 0));
//			note.setSortType(safeInt(e.getAttribute("sortType"), 0));
//			if(note.getType()!=Note.TYPE_IMAGE){
//				try {
//					note.setData(e.getAttribute("textContent")!=null?e.getAttribute("textContent").getBytes("utf-8"):null);
//				} catch (UnsupportedEncodingException e1) {
//					// TODO Auto-generated catch block
//					e1.printStackTrace();
//				}
//			}
//			//Dates
//			SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
//			try {
//				note.setStartDate(format.parse(e.getAttribute("startDate")));
//				if(note.getType()==Note.TYPE_EVENT){
//					Calendar c = Calendar.getInstance();
//					c.setTime(note.getStartDate());
//					c.add(c.MINUTE, -user.getMinuteOffset());
//					note.setStartDate(c.getTime());
//				}
//			} catch (Exception e2) {
//			}
//			try {
//				note.setEndDate(format.parse(e.getAttribute("endDate")));
//				if(note.getType()==Note.TYPE_EVENT){
//					Calendar c = Calendar.getInstance();
//					c.setTime(note.getEndDate());
//					c.add(c.MINUTE, -user.getMinuteOffset());
//					note.setEndDate(c.getTime());
//				}
//			} catch (Exception e2) {
//			}
//			
//			return note;			
//		} catch (HibernateException e2) {
//			throw new DBErrorException();
//		}finally{
//			session.close();
//		}
//	}
		
	//Short format
	public static Element getStarredItems(int userID) throws DBErrorException{
		XMLDocumentBuilder doc = new XMLDocumentBuilder();
		DBSession session = new DBSession();
		try {
			List<Star> notes = session.createQuery("from Star " +
					"where user=:user and note.archived=0 order by id").
					setEntity("user", UsersDAO.getCurrent(session, userID)).list();
			for (Star star : notes) {
				Element e = doc.createElement("note");
				noteToElement(star.getNote(), e);
				doc.getRootElement("list").appendChild(e);
			}
		}catch (HibernateException e) {
			throw new DBErrorException();
		} finally {
			session.close();
		}
		return doc.getRootElement();
	}
	
	//Short format
	public static List<Note> getChildrenList(int parent, boolean loadTags) throws DBErrorException{
		XMLDocumentBuilder doc = new XMLDocumentBuilder();
		DBSession session = new DBSession();
		try {
			Note note = (Note) session.get(Note.class, parent);
			List<Note> notes = session.createQuery("from Note n " +
					"where n.left>:left and n.right<:right and level=:level " +
					"and n.user=:user and n.archived=0 order by "+note.getOrderBy()).
						setEntity("user", note.getUser()).
						setInteger("left", note.getLeft()).
						setInteger("right", note.getRight()).
						setInteger("level", note.getLevel()+1).
					list();
			if(loadTags){
				for (Note note2 : notes) {
					List<Tag> tagList = session.createQuery("from Tag where note=:note order by name").
					setEntity("note", note2).
						list();
					String tags = "";
					for (Tag tag : tagList) {
						if(!"".equals(tags))
							tags += ", ";
						tags += tag.getName();
					}
					note2.setTagsString(tags);
				}				
			}
			return notes;
		}catch (HibernateException e) {
			throw new DBErrorException();
		} finally {
			session.close();
		}
	}
	//Short format
	public static Node getChildren(int parent, int userID) throws DBErrorException{
		XMLDocumentBuilder doc = new XMLDocumentBuilder();
		DBSession session = new DBSession();
		try {
			Note note = (Note) session.get(Note.class, parent);
			List<Note> notes = session.createQuery("from Note n " +
					"where n.left>:left and n.right<:right and level=:level " +
					"and n.user=:user and n.archived=0 order by "+note.getOrderBy()).
						setEntity("user", note.getUser()).
						setInteger("left", note.getLeft()).
						setInteger("right", note.getRight()).
						setInteger("level", note.getLevel()+1).
					list();
			for (Note n : notes) {
				Element e = doc.createElement("note");
				noteToElement(n, e);
				doc.getRootElement().appendChild(e);
			}
		}catch (HibernateException e) {
			throw new DBErrorException();
		} finally {
			session.close();
		}
		return doc.getRootElement();
	}
	
	public static List<Element> NodeListToList(NodeList nl){
		List<Element> res = new ArrayList<Element>();
		for (int i = 0; i < nl.getLength(); i++) {
			if(nl.item(i) instanceof Element)
				res.add((Element) nl.item(i));
		}
		return res;
	}
	
	public static Element getTaggedNotes(int currentNoteID, int userID, String tag) throws DBErrorException{
		XMLDocumentBuilder doc = new XMLDocumentBuilder();
		DBSession session = new DBSession();
		try {
			Note note = (Note) session.get(Note.class, currentNoteID);
			List<Note> roots = session.createQuery("select p.note from Permission p " +
					"where p.note.user=:user and p.user=:me " +
					"and p.note.left<=:left and p.note.right>=:right").
						setEntity("user", note.getUser()).
						setEntity("me", UsersDAO.getCurrent(session, userID)).
						setInteger("left", note.getLeft()).
						setInteger("right", note.getRight()).
					list();
			if(roots.size()>0){
				List<Note> notes = session.createQuery("select n from Note n, Tag t where t.note=n" +
						" and t.name=:tag and n.user=:user and n.archived=0" +
						" and n.left>=:left and n.right<=:right order by "+roots.get(0).getOrderBy()).
							setString("tag", tag).
							setEntity("user", roots.get(0).getUser()).
							setInteger("left", roots.get(0).getLeft()).
							setInteger("right", roots.get(0).getRight()).
						list();
				for (Note note2 : notes) {
					Element e = doc.createElement("note");
					noteToElement(note2, e);
					doc.getRootElement("list").appendChild(e);
				}
			}
		}catch (HibernateException e) {
			throw new DBErrorException();
		} finally {
			session.close();
		}
		return doc.getRootElement();
		
	}
	
	public enum TagType {MyTags, TreeTags, AllTags};
	
	public static Node getTags(int noteID, TagType tagType, int userID) throws DBErrorException{
		XMLDocumentBuilder doc = new XMLDocumentBuilder();
		DBSession session = new DBSession();
		try {
			Note note = (Note) session.get(Note.class, noteID);
			Query query = null;
			switch (tagType) {
			case MyTags:
				query = session.createQuery("select t.name, 0 from Tag t where t.note=:note" +
						" order by t.name").setEntity("note", note);
				break;
			case TreeTags:
				query = session.createQuery("select t.name, count(*) from Tag t, Note n " +
						"where t.note=n and n.left>=:left and n.right<=:right and n.user=:user and n.archived=0 " +
						"group by t.name order by t.name").
							setInteger("left", note.getLeft()).
							setInteger("right", note.getRight()).
							setEntity("user", note.getUser());
				break;
			case AllTags:
				List<Note> roots = session.createQuery("select p.note from Permission p " +
						"where p.note.user=:user and p.user=:me " +
						"and p.note.left<=:left and p.note.right>=:right").
							setEntity("user", note.getUser()).
							setEntity("me", UsersDAO.getCurrent(session, userID)).
							setInteger("left", note.getLeft()).
							setInteger("right", note.getRight()).
						list();
				if(roots.size()>0){
					Note root = roots.get(0);
					query = session.createQuery("select t.name, count(*) from Tag t, Note n " +
							"where t.note=n and n.left>=:left and n.right<=:right and n.user=:user and n.archived=0 " +
							"group by t.name order by t.name").
								setInteger("left", root.getLeft()).
								setInteger("right", root.getRight()).
								setEntity("user", root.getUser());					
				}
				break;
			}
			if(query!=null){
				List<Object[]> res = query.list();
				for (Object[] arr: res) {
					Element e = doc.createElement("tag");
					e.setAttribute("name", arr[0].toString());
					e.setAttribute("code", arr[0].toString());
					int count = ((Number)arr[1]).intValue();
					if(count>1){
						e.setAttribute("name", arr[0]+"("+arr[1]+")");
					}
					doc.getRootElement().appendChild(e);
				}				
			}
		}catch (HibernateException e) {
			throw new DBErrorException();
		} finally {
			session.close();
		}
		return doc.getRootElement();
	}
	
	public static boolean starItem(int userID, int noteID)throws DBErrorException, NoPermissionException{
		DBSession session = new DBSession();
		try {
			Note note = (Note) session.get(Note.class, noteID);
			if(!PermissionsDAO.hasPermission(session, userID, note, PermissionType.VIEW))
				throw new NoPermissionException();
			List<Star> star = session.createQuery("from Star where user=:user and note=:note").
					setEntity("user", UsersDAO.getCurrent(session, userID)).
					setEntity("note", note).
					list();
			session.begin();
			boolean result = false;
			if(star.size()>0){
				session.getSession().delete(star.get(0));
			}else{
				Star newStar = new Star();
				newStar.setNote(note);
				newStar.setUser(UsersDAO.getCurrent(session, userID));
				session.save(newStar);
				result = true;
			}
			session.commit();
			return result;
		} catch (HibernateException e) {
			e.printStackTrace();
			session.rollback();
			throw new DBErrorException();
		} finally {
			session.close();
		}
	}
	
	public static void changeSort(int userID, int noteID, int sortType, int sortDirection)
			throws DBErrorException, NoPermissionException{
		DBSession session = new DBSession();
		try {
			Note note = (Note) session.get(Note.class, noteID);
			if(!PermissionsDAO.hasPermission(session, userID, note, PermissionType.EDIT))
				throw new NoPermissionException();
			session.begin();
			note.setSort(sortType);
			note.setSortType(sortDirection);
			session.save(note);
			session.commit();
		} catch (HibernateException e) {
			e.printStackTrace();
			session.rollback();
			throw new DBErrorException();
		} finally {
			session.close();
		}		
	}
	
	public static int archiveNote(int userID, int noteID) throws DBErrorException, NoPermissionException{
		DBSession session = new DBSession();
		try {
			Note note = (Note) session.get(Note.class, noteID);
			if(!PermissionsDAO.hasPermission(session, userID, note, PermissionType.DELETE))
				throw new NoPermissionException();
			User user = UsersDAO.getCurrent(session, userID);
			Note parent = (Note) session.createQuery("select n from Note n " +
					"where n.left<:left and n.right>:right and n.level=:level and n.user=:user").
					setInteger("left", note.getLeft()).
					setInteger("right", note.getRight()).
					setInteger("level", note.getLevel()-1).
					setEntity("user", user).
				uniqueResult();
			if(parent==null){
				throw new NoPermissionException();				
			}
			session.begin();
			int left = note.getLeft();
			int right = note.getRight();
			
			List<Note> notes = session.createQuery("from Note n " +
					"where n.right<=:right and n.left>=:left and n.user=:user").
					setInteger("left", left).
					setInteger("right", right).
					setEntity("user", user).
				list();
			for (Note n : notes) {
				n.setArchived(1);
				session.save(n);
				List<Star> stars = session.createQuery("from Star where note=:note").
					setEntity("note", n).list();
				for (Star star : stars) {
					session.getSession().delete(star);
				}
			}
			session.commit();
			return parent.getId();
		} catch (HibernateException e) {
			e.printStackTrace();
			session.rollback();
			throw new DBErrorException();
		} finally {
			session.close();
		}		
	}
	
	private static String printNote(Note n){
		SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss Z");
		return "startDate: "+df.format(n.getStartDate())+", endDate: "+df.format(n.getEndDate());
	}
	
	private static boolean moveNode(Note note, int parentID, int siblingID, DBSession session, User parentUser, User noteUser){
		int left = note.getLeft();
		int right = note.getRight();
		int level = note.getLevel();
		List<Note> notes = session.createQuery("from Note n " +
				"where n.left >= :left and n.right <= :right and n.user = :user").
				setInteger("left", left).
				setInteger("right", right).
				setEntity("user", noteUser).list();
		for (Note n : notes) {
			n.setLocked(1);
			session.save(n);
		}
		
		notes  = session.createQuery("from Note n " +
				"where n.right>:right and n.user=:user and n.locked=0").
				setInteger("right", right).
				setEntity("user", noteUser).
				list();
		int delta = right-left+1;
		for (Note n : notes) {
			n.setRight(n.getRight()-delta);
			if(n.getLeft()>left)
				n.setLeft(n.getLeft()-delta);
			session.save(n);
		}
		
		Note parent = (Note) session.getSession().get(Note.class, parentID);
		
		int newLeft = parent.getRight();
		if(siblingID!=-1){
			Note sibling = (Note) session.getSession().get(Note.class, siblingID);
			newLeft = sibling.getLeft();
		}
		
		notes = session.createQuery("from Note n " +
				"where n.right>=:right and n.user=:user and n.locked=0").
				setInteger("right", newLeft).
				setEntity("user", parentUser).
				list();
		for (Note n : notes) {
			n.setRight(n.getRight()+delta);
			if(n.getLeft()>=newLeft)
				n.setLeft(n.getLeft()+delta);
			session.save(n);
		}
		
		notes  = session.createQuery("from Note n " +
				"where n.user=:user and n.locked=1").
				setEntity("user", noteUser).
				list();
		delta = left-newLeft;
		for (Note n : notes) {
			n.setRight(n.getRight()-delta);
			n.setLeft(n.getLeft()-delta);
			n.setLevel(n.getLevel()+parent.getLevel()-level+1);
			n.setLocked(0);
			n.setUser(parentUser);
			session.save(n);
		}
		return true;
	}
	
	public static int moveNoteUp(int userID, int noteID) 
			throws DBErrorException, NoPermissionException{
		DBSession session = new DBSession();
		try {
			Note note = (Note) session.get(Note.class, noteID);
			if(!PermissionsDAO.hasPermission(session, userID, note, PermissionType.EDIT))
				throw new NoPermissionException();
			if(note.getLevel()==0)
				throw new NoPermissionException();				
			User user = note.getUser();
			Note parent = (Note) session.createQuery("select n from Note n " +
			"where n.user=:user and n.right>:right and n.left<:left and n.level=:level").
					setEntity("user", user).
					setInteger("left", note.getLeft()).
					setInteger("right", note.getRight()).
					setInteger("level", note.getLevel()-1).
				uniqueResult();
			List<Note> siblings = session.createQuery("select n from Note n " +
					"where n.user=:user and n.right<=:right and n.left>:pleft and n.level=:level and n.archived=0 order by n.right desc").
					setInteger("level", note.getLevel()).
					setEntity("user", user).
					setInteger("right", note.getLeft()-1).
					setInteger("pleft", parent.getLeft()).
				list();
			if(siblings.size()==0)
				return parent.getId();
			session.begin();
			moveNode(note, parent.getId(), siblings.get(0).getId(), session, user, user);
			session.commit();
			return parent.getId();
		} catch (HibernateException e) {
			e.printStackTrace();
			session.rollback();
			throw new DBErrorException();
		} finally {
			session.close();
		}		
	}
	
	public static int moveNoteDown(int userID, int noteID) 
			throws DBErrorException, NoPermissionException{
		DBSession session = new DBSession();
		try {
			Note note = (Note) session.get(Note.class, noteID);
			if(!PermissionsDAO.hasPermission(session, userID, note, PermissionType.EDIT))
				throw new NoPermissionException();
			if(note.getLevel()==0)
				throw new NoPermissionException();				
			User user = note.getUser();
			Note parent = (Note) session.createQuery("select n from Note n " +
			"where n.user=:user and n.right>:right and n.left<:left and n.level=:level").
					setEntity("user", user).
					setInteger("left", note.getLeft()).
					setInteger("right", note.getRight()).
					setInteger("level", note.getLevel()-1).
				uniqueResult();			
			List<Note> siblings = session.createQuery("select n from Note n " +
					"where n.user=:user and n.left>:left and n.right<:pright and n.level=:level and n.archived=0 order by n.left").
					setInteger("level", note.getLevel()).
					setEntity("user", user).
					setInteger("left", note.getLeft()).
					setInteger("pright", parent.getRight()).
				list();
//			System.out.println("Siblings = "+siblings.size());
			session.begin();
			if(siblings.size()==0)
				return parent.getId();
			int siblingID = -1;
			if(siblings.size()>=2)
				siblingID = siblings.get(1).getId();
			moveNode(note, parent.getId(), siblingID, session, user, user);
			session.commit();
			return parent.getId();
		} catch (HibernateException e) {
			e.printStackTrace();
			session.rollback();
			throw new DBErrorException();
		} finally {
			session.close();
		}		
	}
	
	public static boolean moveNote(int userID, int parentID, int noteID)
			throws DBErrorException, NoPermissionException{
		DBSession session = new DBSession();
		try {
			Note parent = (Note) session.get(Note.class, parentID);
			if(!PermissionsDAO.hasPermission(session, userID, parent, PermissionType.CREATE))
				throw new NoPermissionException();
//			Permission root = PermissionsDAO.getPermission(session, userID, parent);
//			if(root.getId()!=rootID)
//				throw new NoPermissionException();
			Note note = (Note) session.get(Note.class, noteID);
			if(note.getLevel()==0)
				throw new NoPermissionException();
			if(note!=null 
					&& (parent.getLeft()<note.getLeft() || parent.getRight()>note.getRight() || 
							parent.getUser().getId()!=note.getUser().getId())){
				
				if(!PermissionsDAO.hasPermission(session, userID, note, PermissionType.DELETE))
					throw new NoPermissionException();
				
				session.begin();
				moveNode(note, parentID, -1, session, parent.getUser(), note.getUser());
				session.commit();
				return true;
			}
		} catch (HibernateException e) {
			e.printStackTrace();
			session.rollback();
			throw new DBErrorException();
		} finally {
			session.close();
		}		
		
		return false;
	}
	
	public static net.fortuna.ical4j.model.Calendar getICalCalendar(String shareCode, boolean addTasks){
		DBSession session = new DBSession();
		try {
			List<Note> notes = session.createQuery("from Note where shareCode=:code")
				.setString("code", shareCode)
			.list();
			if(notes.size()>0){
				Note note  = notes.get(0);
				net.fortuna.ical4j.model.Calendar cal = new net.fortuna.ical4j.model.Calendar();
				cal.getProperties().add(new ProdId("-//BeOrganized!//iCal4j 1.0//EN"));
				cal.getProperties().add(Version.VERSION_2_0);
				cal.getProperties().add(CalScale.GREGORIAN);
				cal.getProperties().add(new XProperty("X-WR-CALNAME", "BeOrganized! ("+note.getName()+")"));
				TimeZoneRegistry registry = 
					TimeZoneRegistryFactory.getInstance().createRegistry();
				VTimeZone tz = registry.getTimeZone(note.getUser().getTimeZone()).getVTimeZone();
				cal.getComponents().add(tz);
				
				List<Note> result = null;
				if(addTasks){
					result = session.createQuery("from Note n where n.user=:user" +
							" and n.left>=:left and n.right<=:right and n.archived=0" +
							" and ((n.type=:type_app) or (n.type=:type_task and n.completed=0))").
						setEntity("user", note.getUser()).
						setInteger("left", note.getLeft()).
						setInteger("right", note.getRight()).
						setInteger("type_app", Note.TYPE_EVENT).
						setInteger("type_task", Note.TYPE_TASK).
					list();					
				}else{
					result = session.createQuery("from Note n where n.user=:user" +
							" and n.left>=:left and n.right<=:right and n.archived=0" +
							" and n.type=:type_app").
						setEntity("user", note.getUser()).
						setInteger("left", note.getLeft()).
						setInteger("right", note.getRight()).
						setInteger("type_app", Note.TYPE_EVENT).
					list();
				}
				
				for (Note n : result) {
					if(n.getType()==Note.TYPE_TASK && null==n.getStartDate())
						continue;
					n.initRecurrence(true);
					VEvent event  = null;
					if(n.getType()==Note.TYPE_TASK){
						event = new VEvent(
								new net.fortuna.ical4j.model.Date(n.getStartDate()), n.getName());						
					}else{
						Calendar c = Calendar.getInstance();
						c.setTime(n.getStartDate());
						c.add(Calendar.MINUTE, note.getUser().getMinuteOffsetByDate(n.getStartDate()));
						
						Calendar c2 = Calendar.getInstance();
						c2.setTime(n.getEndDate());
						c2.add(Calendar.MINUTE, note.getUser().getMinuteOffsetByDate(n.getStartDate()));
						
						event = new VEvent(
								new DateTime(c.getTime()), new DateTime(c2.getTime()), n.getName());
						event.getProperties().add(tz.getTimeZoneId());						
					}
					if(n.isRecurred()){
						String rRule = "FREQ=";
						switch (n.getRecurrencePeriod()) {
						case Note.REC_PERIOD_DAYS:
							rRule += "DAILY";
							break;
						case Note.REC_PERIOD_WEEKS:
							rRule += "WEEKLY;WKST=MO";
							break;
						case Note.REC_PERIOD_MONTHS:
							rRule += "MONTHLY";
							break;
						default:
							rRule += "YEARLY";
							break;
						}
						if(n.getRecurrenceDays()>1)
							rRule += (";INTERVAL="+n.getRecurrenceDays());
						try {
							event.getProperties().add(new RRule(rRule));
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
					event.getProperties().add(new Uid(n.getShareCode()));
					cal.getComponents().add(event);
				}
				return cal;
			}
		} catch (HibernateException e) {
			
		}finally{
			session.close();
		}
		return null;
	}
	
	public static Node getTagList(int noteID)throws DBErrorException{
		XMLDocumentBuilder doc = new XMLDocumentBuilder();
		DBSession session = new DBSession();
		try {
			Note note = (Note) session.get(Note.class, noteID);
			List<Tag> tags = session.createQuery("from Tag where note=:note order by name").
					setEntity("note", note).list();
			for (Tag tag : tags) {
				Element e = doc.createElement("tag");
				e.setAttribute("id", ""+tag.getId());
				e.setAttribute("name", tag.getName());
				doc.getRootElement().appendChild(e);
			}
		} catch (Exception e) {
			throw new DBErrorException();
		} finally {
			session.close();
		}
		return doc.getRootElement();		
	}
	
	public static void uploadData(int noteID, int userID, byte[] data, String fileName, int fileSize, String contentType) throws DBErrorException, NoPermissionException{
		DBSession session = new DBSession();
		try {
			Note n = (Note) session.get(Note.class, noteID);
			if(!PermissionsDAO.hasPermission(session, userID, n, PermissionType.EDIT))
				throw new NoPermissionException();
			session.begin();
			if(fileName!=null)
				n.setFileName(fileName);
			n.setFileSize(fileSize);
			if(contentType!=null)
				n.setContentType(contentType);
			n.setData(data);
			session.commit();
		} catch (HibernateException e) {
			e.printStackTrace();
			session.rollback();
			throw new DBErrorException();
		}
	}
	
	public static Note saveNote(ServletContext servletContext, Note note, boolean newNote, 
			String tags, boolean saveTags, int userID, boolean saveAfterImport)
			throws DBErrorException, NoPermissionException, InvalidNameException, InvalidURLException, 
			InvalidStartDateException, InvalidEndDateException, StartDateLessEndDateException, InvalidFileException, URLTooLongException, NameTooShortException{
//		System.out.println("Saving note: ID = "+noteID+", new: "+newNote+", tags: "+tags+", saveTags: "+saveTags);
		DBSession session = new DBSession();
		try {
			Note n = (Note) session.get(Note.class, note.getId());
			Note parent = n;
			session.begin();
			if(newNote){
				if(!PermissionsDAO.hasPermission(session, userID, parent, PermissionType.CREATE))
					throw new NoPermissionException();
				n = new Note();
				n.setUser(parent.getUser());
				if("".equals(note.getShareCode()))
					n.setShareCode(Note.genRandomString());
				else
					n.setShareCode(note.getShareCode());
				n.setCreateDate(new Date());
			}else{
				if(!PermissionsDAO.hasPermission(session, userID, n, PermissionType.EDIT))
					throw new NoPermissionException();
			}
			User user = UsersDAO.getCurrent(session, userID);
			n.setEditUser(user);
			n.setEditDate(new Date());

			if(saveAfterImport){
				n.setShareCode(Note.genRandomString());
				n.setSort(note.getSort());
				n.setSortType(note.getSortType());
				n.setUser(note.getUser());
			}
			
			n.setType(note.getType());
			//Copy all data to actual object
			n.setCustomIcon(note.getCustomIcon());
			if(note.getType()!=Note.TYPE_IMAGE && note.getType()!=Note.TYPE_FILE){
				n.setData(note.getData());				
			}else{
				if(note.getFileSize()>0 || saveAfterImport){
					n.setFileName(note.getFileName());
					n.setFileSize(note.getFileSize());
					n.setContentType(note.getContentType());
					//Write file to FS
					//Storing file to FS
					try {
//						System.out.println("Storing file to FS: "+note.getShareCode());
						BufferedOutputStream outputStream = new BufferedOutputStream(
								new FileOutputStream(servletContext.getRealPath(NotesDAO.FILES_STORE+n.getShareCode()+".bin")));
						outputStream.write(note.getData());
						outputStream.close();
					} catch (Exception e) {
						e.printStackTrace();
						session.rollback();
						throw new InvalidFileException();
					}
				}
			}
			n.setName(note.getName());
			if(n.getName()==null || "".equals(n.getName()))
				throw new InvalidNameException();
			n.setPriority(note.getPriority());
			n.setUrl(note.getUrl());
//			Log.debug(null, "Looking for error["+n.getName()+"]["+note.getStartDate()+"]=>["+n.getStartDate()+"]");
			n.setStartDate(note.getStartDate());
			n.setEndDate(note.getEndDate());
			boolean moveDates = n.getType()==Note.TYPE_EVENT || (n.getType()==Note.TYPE_TASK && n.getStartDate()!=null);
			if(moveDates){
				n.setRecurrence(note.getRecurrence());
			}else{
				n.setRecurrence("");
			}
			n.initRecurrence(moveDates);
			if(!saveAfterImport && n.getCompleted()==0 && note.getCompleted()==1 && moveDates && n.isRecurred()){
				//Moving task to new date
				repeatNote(user, n, session, false);
			}else{
				n.setCompleted(note.getCompleted());
			}
			//Check for errors
			switch (n.getType()) {
			case Note.TYPE_IMAGE:
				if(newNote && n.getFileSize()==0)
					throw new InvalidFileException();
				break;
			case Note.TYPE_FILE:
				if(newNote && n.getFileSize()==0)
					throw new InvalidFileException();
				break;
			case Note.TYPE_EVENT:
				if(n.getStartDate()==null){
					throw new InvalidStartDateException();
				}
				if(n.getEndDate()==null){
					throw new InvalidEndDateException();
				}
				if(n.getStartDate().compareTo(n.getEndDate())>0){
					throw new StartDateLessEndDateException();
				}
				break;
			case Note.TYPE_BMARK:
				if(n.getUrl()==null){
					throw new InvalidURLException();
				}
				if(n.getUrl().length()==0){
					throw new InvalidURLException();
				}
				if(n.getUrl().length()>=255)
					throw new URLTooLongException();
				break;
			}
			
			
			if(newNote){
				n.setLevel(parent.getLevel()+1);
				n.setLeft(parent.getRight());
				n.setRight(n.getLeft()+1);
				session.createQuery("update Note n set n.right=n.right+2 where n.right>=:right and n.user=:user").
						setInteger("right", n.getLeft()).
						setEntity("user", n.getUser()).
					executeUpdate();
				session.createQuery("update Note n set n.left=n.left+2 where n.left>=:left and n.user=:user").
						setInteger("left", n.getLeft()).
						setEntity("user", n.getUser()).
					executeUpdate();
			}
			if(saveTags){
				if(!newNote){
					session.createQuery("delete from Tag t where t.note=:note").
							setEntity("note", n).
						executeUpdate();					
				}
				session.save(n);
				if(tags!=null){
					StringTokenizer st = new StringTokenizer(tags, ",");
					while(st.hasMoreTokens()){
						String tag = st.nextToken().trim().toLowerCase();
						if(tag.equals("") || tag.indexOf("/")!=-1)
							continue;
						Tag t = new Tag();
						t.setName(tag);
						n.getTags().add(t);
						t.setNote(n);
						session.save(t);
					}
				}
			}
			session.save(n);
			session.commit();
			return n;
		} catch (HibernateException e) {
			e.printStackTrace();
			session.rollback();
			throw new DBErrorException();
		}
	}
	
	private static boolean repeatNote(User user, Note note, DBSession session, boolean aForceRepeat){
		if(note.getType()==Note.TYPE_TASK || note.getType()==Note.TYPE_EVENT){
			Calendar today = Note.getUserNow(user);
			if(note.getType()==Note.TYPE_EVENT){
//				today = Note.getUserNow(user);
				if(Note.compareDates(note.getEndDate(), user, today, null)<=0 && !aForceRepeat)
					return false;
			}
			Calendar cal = Calendar.getInstance();
			Calendar cal2 = Calendar.getInstance();
			
			cal.setTime(note.getStartDate());
			
			if(note.getType()==Note.TYPE_EVENT){
				cal2.setTime(note.getEndDate());
			}
			if(note.getRecurrenceType()==Note.REC_TYPE_AFTER && !aForceRepeat){
				cal.set(Calendar.YEAR, today.get(Calendar.YEAR));
				cal.set(Calendar.DAY_OF_YEAR, today.get(Calendar.DAY_OF_YEAR));
				cal2.set(Calendar.YEAR, today.get(Calendar.YEAR));
				cal2.set(Calendar.DAY_OF_YEAR, today.get(Calendar.DAY_OF_YEAR));
			}
			do{
				switch (note.getRecurrencePeriod()) {
				case Note.REC_PERIOD_DAYS:
					cal.add(Calendar.DAY_OF_YEAR, note.getRecurrenceDays());
					cal2.add(Calendar.DAY_OF_YEAR, note.getRecurrenceDays());
					break;
				case Note.REC_PERIOD_WEEKS:
					cal.add(Calendar.WEEK_OF_YEAR, note.getRecurrenceDays());
					cal2.add(Calendar.WEEK_OF_YEAR, note.getRecurrenceDays());
					break;
				case Note.REC_PERIOD_MONTHS:
					cal.add(Calendar.MONTH, note.getRecurrenceDays());
					cal2.add(Calendar.MONTH, note.getRecurrenceDays());
					break;
				default:
					cal.add(Calendar.YEAR, note.getRecurrenceDays());
					cal2.add(Calendar.YEAR, note.getRecurrenceDays());
				}
				note.setStartDate(cal.getTime());
				if(note.getType()==Note.TYPE_EVENT){
					note.setEndDate(cal2.getTime());
				}
			}while(Note.compareDates(note.getStartDate(), 
					note.getType()==Note.TYPE_EVENT?user:null, today.getTime(), null)<=0);
		}
		return true;
	}
}
